# coding=utf-8

"""
================================================
@author : YX
@time   : 2024-03-08 11:55
@desc   : 爬虫，阿里 / 高德系 yueyuechuxing.cn 专用
@usage  : 1. 安装 python2.7
          2. > pip install requests
          3. 登录账号，抓取 _admin_sign_ 和 _admin_tk_ 字段，更新到本文件的请求变量中保存
          4. > python go_page_list.py
@output : 1. ./data/data_full_地名编码.txt      - 原始返回数据（清洗过，接近原始）
          2. ./data/data_id_地名编码.txt        - 纯 id 列表
          3. ./data/data_parsed_地名编码.txt    - 格式化数据，可直接按文件导入 Excel（导入时使用 | 作为分隔符）
          4. ./res_地名编码_20240308v1.xlsx     - 最终数据文件（在 Excel 中导入 data_parsed_地名编码.txt 而得到）
================================================
"""

import sys
import io
import requests
import json
import time
import random

# 设定系统默认编码
reload( sys )
sys.setdefaultencoding( 'utf-8' )
print( "sys_stdout_encoding : " + sys.stdout.encoding ) # 运行日志

# 数据请求地址
str_url = "https://admin.yueyuechuxing.cn/admin/v1/assets/driver/pageList"

# 数据请求权限
str_admin_sign = "76828b6260c0588b86dff532333faab9"
str_admin_tk = "8fb82314aef84051876cc87bed72e9b0"

# 请求 payload 通用配置
dict_payload_template = {
  "pageSize": 50,
  "idCardStatus": [],
  "driverLicenseStatus": [],
  "driverHailingLicenseStatus": [],
  "supervisionCardStatus": [],
  "carLicenseStatus": [],
  "compulsoryTrafficInsuranceStatus": [],
  "commercialInsuranceStatus": [],
  "carHailingLicenseStatus": [],
  "taxiTransportLicenseStatus": [],
  "tagIds": [],
  "highQualityFlag": None,
  "orderDisposeStates": [],
  "preOrderDisposeStates": [],
  "queryCarTypes": []
}

# 请求头通用配置
dict_headers_template = {
  '_admin_current_page': '/assets/driverManage/driverList',
  'sec-ch-ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
  '_admin_resource_key': 'driverManage',
  'sec-ch-ua-mobile': '?0',
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Content-Type': 'application/json',
  'Accept': 'application/json, text/plain, */*',
  '_admin_eid': '800189',
  '_admin_session_eid': '800605',
  'sec-ch-ua-platform': '"macOS"',
  'host': 'admin.yueyuechuxing.cn'
}

# 随机休息时间上限（模拟真人操作）
int_sleep_time_max = 5

# 高价值字段名
arr_keys = [ 'mobile', 'name', 'idCardNo', 'bizType', 'employeeType', 'plateNo', 'adcode', 'tenantId', 'orderCount', 'state', 'gmtCreate', 'orderDisposeState', 'disposeStateStr', 'carId', 'carTeamId', 'operateAmount', 'source' ]
# id 字段名
str_key_id = "id"
# 地名编码字段名
str_key_adcode = "adcode"

# 城市编码、城市名（待补齐，目前不用这个分支）
arr_adcode_data = {
    "guangxi" : [
        { "adcode":"451000", "cityName":"baise" },
        { "adcode":"450600", "cityName":"fangchenggang" },
        { "adcode":"450300", "cityName":"guilin" },
        { "adcode":"450200", "cityName":"liuzhou" },
        { "adcode":"450100", "cityName":"nanning" },
        { "adcode":"450400", "cityName":"wuzhou" }
    ],
    "guangdong" : [
        { "adcode":"440100", "cityName":"guangzhou" },
        { "adcode":"440300", "cityName":"shenzhen" }
    ]
}

def get_page_one_with_adcode( str_adcode, int_page_num ) :
    """
    发起网络请求，按城市读取一页资料

    @param str_adcode   : 城市编码
    @param int_page_num : 页码
    @return : 网络请求返回结果
    """
    # 拼凑真实请求 payload
    dict_payload = dict_payload_template
    dict_payload.update({
      "pageNum" : int_page_num,
      "adcode" : str_adcode
    })
    json_payload = json.dumps( dict_payload )
    # 拼凑真实请求头
    dict_headers = dict_headers_template
    dict_headers.update({
      '_admin_sign' : str_admin_sign,
      '_admin_ts' : str( int(time.time()) ),
      '_admin_tk' : str_admin_tk,
      '_admin_session_adcode' : str_adcode
    })
    response = requests.request( "POST", str_url, headers=dict_headers, data=json_payload )
    return response.text

def get_page_one_without_adcode( int_page_num ) :
    """
    发起网络请求，按城市读取一页资料

    @param int_page_num : 页码
    @return : 网络请求返回结果
    """
    # 拼凑真实请求 payload
    dict_payload = dict_payload_template
    dict_payload.update({
      "pageNum" : int_page_num
    })
    json_payload = json.dumps( dict_payload )
    # 拼凑真实请求头
    dict_headers = dict_headers_template
    dict_headers.update({
      '_admin_sign' : str_admin_sign,
      '_admin_ts' : str( int(time.time()) ),
      '_admin_tk' : str_admin_tk
    })
    response = requests.request( "POST", str_url, headers=dict_headers, data=json_payload )
    return response.text

def parse_page_one( str_text, str_location_name ) :
    """
    解析一次网络请求的返回结果，一页 50 条（以内）
    1、json 写入【data_full_地名编码.txt】
    2、id 写入【data_id_地名编码.txt】
    3、筛选高价值字段，按导入 excel 的格式写入【data_parsed_地名编码.txt】

    @param str_text             : 网络请求结果
    @param str_location_name    : 地名
    @return	: 总页数（按一页 50 条算）
    """
    json_text = json.loads(str_text)
    int_page_total = json_text["data"]["totalPage"]
    ##print( int_page_total ) # 测试日志
    json_items = json_text["data"]["items"]
    ##print( len(json_items) ) # 测试日志
    # 这层循环是解析一次请求的 50 条（以内的）结果
    for json_item in json_items :
        # 清洗数据
        #     1、把 unicode 转化为中文
        #     2、根据实际情况，有些数据存在多余的空格和制表符，需要清洗掉
        str_data_full = str( json_item ).decode( 'unicode_escape' ).replace( '\t ', '' ).replace( '\t', '' ).replace( '\n预约单', ',预约单' )
        ##print( str_data_full ) # 测试日志
        # json 写入【data_full_地名编码.txt】
        str_filename_full = "data_full_" + str( json_item[str_key_adcode] ) + ".txt" 
        file_full = open( str_filename_full, 'a' )
        file_full.write( str_data_full )
        file_full.write( "\n" )
        file_full.close()
        # id 写入【data_id_地名编码.txt】
        str_data_id = str( json_item["id"] )
        ##print(str_data_id) # 测试日志
        str_filename_id = "data_id_" + str( json_item[str_key_adcode] ) + ".txt"
        file_id = open( str_filename_id, 'a' )
        file_id.write( str_data_id )
        file_id.write( "\n" )
        file_id.close()
        # 筛选高价值字段，按导入 excel 的格式写入【data_parsed_地名编码.txt】
        str_filename_parsed = "data_parsed_all.txt"
        file_parsed = open( str_filename_parsed, 'a' )
        str_data_one = "" + str( json_item[str_key_id] )
        # 这层循环是按照高价值字段提炼数据
        for key in arr_keys :
            if key == "gmtCreate" :
                # 如果是时间戳字段则转化为可读格式
                str_data_one +=  '|' + str( time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime( json_item[key]/1000 ) ) )
            else :
                str_data_one +=  '|' + str( json_item[key] ).replace( '\t ', '' ).replace( '\t', '' ).replace( '\n预约单', ',预约单' )
        ##print( str_data_one ) # 测试日志
        file_parsed.write( str_data_one )
        file_parsed.write( "\n" )
        file_parsed.close()
        ##print "done_id: " + str_data_id # 测试日志
    return int_page_total

def parse_from_file_full( str_location_name ) :
    """
    从特定文件【data_full_地名编码.txt】解析 json 文本，将高价值字段按导入 excel 的格式，写入【data_parsed_地名编码.txt】

    @param str_location_name    : 地名
    @return	: None
    """
    str_filename_full = "data_full_" + str_location_name + ".txt"
    str_filename_parsed = "data_parsed_" + str_location_name + ".txt"
    file_parsed = open( str_filename_parsed, 'a' )
    str_title = str_key_id
    for key in arr_keys :
        str_title +=  '|' + key
    print( str_title ) # 运行日志
    file_parsed.write( str_title )
    file_parsed.write( "\n" )
    file_full = open( str_filename_full, 'r' )
    # 这层循环是读取单个文件（城市）的每一行数据
    for str_line in file_full :
        ##print( str_line ) # 测试日志
        # 需要改动源数据格式才能使用 json.loads()
        #     1、' 要变成 "
        #     2、u' 要变成 " (两种正则条件)
        #     3、None 要变成 null
        json_line = json.loads( str_line.replace( "'", '"' ).replace( '{u"', '{ "' ).replace( ' u"', ' "' ).replace( 'None', 'null' ) )
        str_data_one = "" + str( json_line[str_key_id] )
        # 这层循环是按照高价值字段提炼数据
        for key in arr_keys :
            if key == "gmtCreate" :
                # 如果是时间戳字段则转化为可读格式
                str_data_one +=  '|' + str( time.strftime( '%Y-%m-%d %H:%M:%S', time.gmtime( json_line[key]/1000 ) ) )
            else :
                str_data_one +=  '|' + str( json_line[key] )
        print( str_data_one ) # 运行日志
        file_parsed.write( str_data_one )
        file_parsed.write( "\n" )
    file_full.close()
    file_parsed.close()
    return

def exec_go_page_list( str_adcode, str_location_name ) :
    """
    按城市发起批量请求和批量解析

    @param str_adcode           : 城市编码
    @param str_location_name    : 地名
    @return	: None
    """
    # 创建最终合并文件、写入标题
    str_filename_parsed = "data_parsed_all.txt"
    file_parsed = open( str_filename_parsed, 'a' )
    str_title = str_key_id
    # 这层循环是拼凑高价值字段标题
    for key in arr_keys :
        str_title +=  '|' + key
    ##print( str_title ) # 测试日志
    file_parsed.write( str_title )
    file_parsed.write( "\n" )
    file_parsed.close()
    print "\n== exec_go_page_list() city: " + str( str_location_name ) + " ==\n" # 运行日志
    # 初始化临时变量
    float_total_time = 0
    float_sleep_time = 0
    int_page_total = 1
    int_iter = 0
    # 这层循环是完成单个城市所有页码的查询
    while int_iter < int_page_total :
        ##print(i) # 测试日志
        str_text = None
        # 根据 adcode 是否为空选择不同的请求方式
        if str_adcode == None :
            str_text = get_page_one_without_adcode( int_iter )
        else :
            str_text = get_page_one_with_adcode( str_adcode, int_iter )
        # 如果请求结果为空，就先结束吧（这里没有验证，也许不是 None，应该是其它字符串，后续有空再慢慢验证）
        if str_text == None :
            break
        # 不为空则继续解析
        int_page_total_tmp = parse_page_one( str_text, str_location_name )
        # 根据第一次请求的结果，更新总页码
        if int_page_total == 1 :
            int_page_total = int_page_total_tmp
        # 更新游标 +1
        int_iter += 1
        # 随机休息时间（模拟真人操作）
        float_sleep_time= random.uniform ( 0, int_sleep_time_max )
        # 单个城市查询总耗时
        float_total_time += float_sleep_time
        # 随机休息
        time.sleep( float_sleep_time )
        print "-- exec_go_page_list() done_page: " + str( int_iter ) + "/" + str( int_page_total ) + " for_city: " + str( str_location_name ) + " time_sleep(s): " + str( round(float_sleep_time,2) ) + " time_total(h): " + str( round(float_total_time/3600,4) ) + " --" # 运行日志
        ##if int_iter > 5 : # 测试专用断点
        ##    break # 测试专用断点
    return

def go_page_list_by_city() :
    """
    入口分支二
    按城市发起批量请求和批量解析
    （本项目暂时没用上，因为全量跑也不多）

    @return	: None
    """
    # 选择省份
    str_province_name = "guangxi"
    #str_province_name = "guangdong"
    # 获取对应省份的数据
    arr_adcode_items = arr_adcode_data[ str_province_name ]
    # 这层循环是遍历当前省份的每一个城市
    for i in range( len( arr_adcode_items ) ) :
        str_adcode = arr_adcode_items[i]["adcode"]
        str_location_name = str_province_name + "_" + arr_adcode_items[i]["cityName"]
        # 执行一个城市
        exec_go_page_list( str_adcode, str_location_name )
    return

def go_page_list_all() :
    """
    入口分支一
    对所有城市发起批量请求和批量解析

    @return	: None
    """
    # 执行所有城市
    exec_go_page_list( None, None )
    return

"""
=======================================
执行入口
    1、选取省份
    2、循环城市编码
    3、按 adcode 批量发起网络请求
    4、中间间隔随机0到5秒，模拟真人点击
=======================================
"""
#go_page_list_by_city()
go_page_list_all()

